from pathlib import Path
import json
import os
import subprocess
import shutil
import argparse


# 创建一个映射表,将需要替换的字符映射为空字符('')
MAPPING_TABLE = str.maketrans(
    {
        "【": "",
        "】": "",
        "《": "",
        "》": "",
        "|": "",
        "｜": "",
        ")": "",
        "(": "",
        "(": "",
        ")": "",
        "{": "",
        "}": "",
        "?": "",
        "[": "",
        "]": "",
        ";": "",
        "#": "",
        "&": "",
        "*": "",
        "@": "",
        "!": "",
        "『": "",
        "』": "",
        "~": "",
        "%": "",
        "^": "",
        "_": "",
        "「": "",
        "」": "",
        "“": "",
        "”": "",
        " ": "",
        ",": "",
        ";": "",
        "/": "",
        "！": "",
        ":": "",
        ",": "",
        ":": "",
        "！": "",
        "？": "",
        "(": "",
        ")": "",
        "—": "",
        "一": "",
        ",": "",
        "，": "",
        "。": "",
        "（": "",
        "）": "",
        "：": "",
        '"': "",
        "‘": "",
        "’": "",
        "‖": "",
    }
)

MP4_SUFFIX = ".MP4"
TEMPORARY_JSON = "TEMPORARY.json"
ERROR_RECORD_LOG = "ERROR_RECORD.LOG"

ENTRY_JSON_FILE = "entry.json"
VIDEO_FILE = "video.m4s"
AUDIO_FILE = "audio.m4s"

KEY_AVID = "avid"
KEY_BVID = "bvid"
KEY_TITLE = "title"
KEY_PAGE_DATA = ["page_data", "page"]
KEY_NEW_FILE_PATH = "NEW_FILE_PATH"

KEY_VIDEO_PATH = "VIDEO_PATH"
KEY_AUDIO_PATH = "AUDIO_PATH"
KEY_ENTRY_JSON_PATH = "ENTRY_JSON_PATH"

KEY_ABSTRACT_TYPE = "ABSTRACT_TYPE"
TYPE_SLIENT = "SLIENT"
TYPE_VOICE = "VOICE"

KEY_FFMPEG_COMMAND = "FFMPEG_COMMAND"
JSON_DATA_LIST = []

HTLP_DOCUMENT = f"""
View the instruction document: python BiliOfflineExtractor.py -h 

Operation mode: python BiliOfflineExtractor.py -p Number
Number:0, represent first process
Number:1, represent second process

After executed: python BiliOfflineExtractor.py -p 0

then to organize and format files: {TEMPORARY_JSON}

Then it can be executed: python BiliOfflineExtractor.py -p 1
"""


def find_file_recursive(folder_path, filename):
    """
    在指定文件夹及其子目录中递归查找指定文件名的文件

    参数:
        folder_path (str): 要搜索的文件夹路径
        filename (str): 要查找的文件名(如 'example.txt')
    返回:
        str | None: 找到的文件的绝对路径,如果未找到则返回 None
    """
    for root, dirs, files in os.walk(folder_path):
        if filename in files:
            file_abs_path = os.path.abspath(os.path.join(root, filename))
            return file_abs_path
    return ""


def read_json_to_dict(json_file_path):
    """
    读取指定路径的 JSON 文件,并将其内容转换为 Python 字典
    参数:
        json_file_path (str): JSON 文件的路径
    返回:
        dict | None: 解析后的字典,如果出错则返回 None
    """
    try:
        with open(json_file_path, "r", encoding="utf-8") as file:
            data = json.load(file)
            return data
    except FileNotFoundError:
        print(f"read_json_to_dict:FileNotFoundError:{json_file_path}")
    except json.JSONDecodeError:
        print(f"read_json_to_dict:illegal JSON Format:{json_file_path}")
    except Exception as e:
        print(f"read_json_to_dict:Unknown Error:{e}")
    return None


def write_string_to_file(content, file_path, write_mode="w"):
    """
    将给定的字符串写入到指定的文件路径中
    参数:
        content (any): 要写入的内容
        file_path (str): 目标文件的路径
    返回:
        bool: 写入成功返回 True,失败返回 False
    """
    try:
        with open(file_path, write_mode, encoding="utf-8") as file:
            file.write(str(content))
        return True
    except Exception as e:
        print(f"Error writing file:{e}")
        return False


def execute_shell_command(shell_command: str):
    """
    执行1个 shell 命令字符串
    参数:
        shell_command (str): 要执行的 shell 命令,
        如 "ls -l" 或 "echo hello"
    """
    try:
        # 使用 shell=True 允许直接传入字符串形式的命令
        subprocess.run(
            shell_command,
            shell=True,  # 允许 shell 命令字符串
            check=True,  # 不自动抛异常,自己判断 returncode
            stdout=subprocess.PIPE,  # 捕获标准输出
            universal_newlines=False,
        )
    except Exception as e:
        exception_info = f"execute_shell_command:occur Exception:{e}"
        print(exception_info)


def move_file(original_file_path: str, new_file_path: str) -> bool:
    """
    将文件从 original_file_path 移动到 new_file_path
    参数:
        original_file_path (str): 源文件路径
        new_file_path (str): 目标文件路径
    返回:
        bool: 移动成功返回 True,失败返回 False
    """
    try:
        # 可选:检查原路径是否存在
        if not os.path.exists(original_file_path):
            error_msg = f"original_file_path not exist:{original_file_path}\n"
            error_msg += f"new_file_path is:{new_file_path}\n"
            write_string_to_file(error_msg, ERROR_RECORD_LOG, "a")

            print(f"original_file_path not exist:{original_file_path}")
            return False
        # 可选:检查原路径是否是文件(而不是目录)
        if not os.path.isfile(original_file_path):
            print(f"original_file_path is not file:{original_file_path}")
            return False
        # 可选:如果目标路径已存在,可以选择提醒或处理
        if os.path.exists(new_file_path):
            print(
                f"new_file_path already exist:{new_file_path},will be covered")

        # 执行移动操作
        shutil.move(original_file_path, new_file_path)
        print(f"success move: {original_file_path} -> {new_file_path}")
        return True
    except PermissionError:
        print(f"move_file:PermissionError,failure")
    except shutil.Error as e:
        print(f"move_file:occur shutil Error:{e}")
    except OSError as e:
        print(f"move_file:OSError:{e}")
    except Exception as e:
        print(f"move_file:unknown error:{e}")
    return False


def get_first_level_subdirectories(dir_path: str) -> list[str]:
    """
    使用 pathlib 获取 dir_path 下的所有一级子目录的绝对路径,并返回 list[str]
    """
    path = Path(dir_path).absolute()  # 转为绝对路径的 Path 对象
    subdirs = []
    try:
        if not path.exists():
            print(
                f"get_first_level_subdirectories:dir_path not exist:{dir_path}")
            return subdirs
        if not path.is_dir():
            print(
                f"get_first_level_subdirectories:dir_path is not directory:{dir_path}")
            return subdirs

        # 遍历一级子项,筛选出目录
        for item in path.iterdir():
            if item.is_dir():
                # 转为字符串形式的绝对路径
                subdirs.append(str(item.absolute()))

    except PermissionError:
        print(f"get_first_level_subdirectories:PermissionError:{dir_path}")
    except Exception as e:
        print(f"get_first_level_subdirectories:unknown error:{e}")
    return subdirs


def create_directory_recursive(new_dir_absolute_path: str) -> bool:
    try:
        os.makedirs(new_dir_absolute_path, exist_ok=True)
        print(f"new_dir_absolute_path alrady exist:{new_dir_absolute_path}")
        return True
    except FileExistsError:
        print(
            f"new_dir_absolute_path alrady exist(warning):{new_dir_absolute_path}")
    except PermissionError:
        print(
            f"PermissionError,can not create folder:{new_dir_absolute_path}")
    except NotADirectoryError:
        print(
            f"path include illegal chars,can not create folder:{new_dir_absolute_path}")
    except OSError as e:
        print(f"OSError,can not create folder:{new_dir_absolute_path}:{e}")
    except Exception as e:
        print(
            f"unknown exception,can not create folder:{new_dir_absolute_path}:{e}")
    return False


def product_full_file_path(result_data, output_dir):
    file_id = ""
    if result_data[KEY_BVID] != "":
        file_id = result_data[KEY_BVID]
    else:
        file_id = result_data[KEY_AVID]

    new_file_path = f"{output_dir}{os.sep}{file_id}-{result_data[KEY_TITLE]}-{result_data[KEY_PAGE_DATA[1]]}-{result_data[KEY_ABSTRACT_TYPE]}{MP4_SUFFIX}"

    result_data[KEY_NEW_FILE_PATH] = new_file_path
    return result_data


def gain_clean_title(entry_data):
    title_string: str = entry_data[KEY_TITLE]
    clean_title = title_string.translate(MAPPING_TABLE)
    return clean_title


def judeg_voice_type(include_word_list: list[str], long_string: str) -> bool:
    for word in include_word_list:
        if word.lower() in long_string.lower():
            print("judeg_voice_type: True")
            return True
    print("judeg_voice_type: False")
    return False


def voice_attribute_configuration(include_word_list, result_data):
    sign = judeg_voice_type(include_word_list, result_data[KEY_TITLE])
    if sign == True:
        # 假如包含include_word,设定为有声
        result_data[KEY_ABSTRACT_TYPE] = TYPE_VOICE
    elif result_data[KEY_AUDIO_PATH] == "":
        # 假如 audio.m4s 不存在,设定为无声
        result_data[KEY_ABSTRACT_TYPE] = TYPE_SLIENT
    else:
        # 假如未包含include_word,设定为无声
        result_data[KEY_ABSTRACT_TYPE] = TYPE_SLIENT
    return result_data


def generate_ffmpeg_script(result_data):
    if result_data[KEY_ABSTRACT_TYPE] == TYPE_VOICE:
        shell_sentence = f"ffmpeg -i {result_data[KEY_VIDEO_PATH]} -i {result_data[KEY_AUDIO_PATH]} -c:v copy -c:a aac -strict experimental {result_data[KEY_NEW_FILE_PATH]}"
        result_data[KEY_FFMPEG_COMMAND] = shell_sentence
        return result_data
    else:
        return result_data


def initialize_record(folder_path):
    record_dict = {}
    video_abs_path = find_file_recursive(folder_path, VIDEO_FILE)
    record_dict[KEY_VIDEO_PATH] = video_abs_path
    audio_abs_path = find_file_recursive(folder_path, AUDIO_FILE)
    record_dict[KEY_AUDIO_PATH] = audio_abs_path

    entry_json_path = find_file_recursive(folder_path, ENTRY_JSON_FILE)
    record_dict[KEY_ENTRY_JSON_PATH] = entry_json_path
    return record_dict


def handle_entry_data(result_data, entry_data):
    result_data[KEY_AVID] = entry_data[KEY_AVID]
    result_data[KEY_BVID] = entry_data[KEY_BVID]
    result_data[KEY_TITLE] = gain_clean_title(entry_data)

    page_result = entry_data[KEY_PAGE_DATA[0]][KEY_PAGE_DATA[1]]
    result_data[KEY_PAGE_DATA[1]] = page_result
    return result_data


def create_json_record(folder_path, output_dir, include_word_list):
    result_data = initialize_record(folder_path)
    entry_data = read_json_to_dict(result_data[KEY_ENTRY_JSON_PATH])

    result_data = handle_entry_data(result_data, entry_data)
    result_data = voice_attribute_configuration(include_word_list, result_data)
    result_data = product_full_file_path(result_data, output_dir)
    result_data = generate_ffmpeg_script(result_data)

    JSON_DATA_LIST.append(result_data)
    write_string_to_file(JSON_DATA_LIST, TEMPORARY_JSON)


def prepare_process(raw_folder_path, output_dir, include_word_list):
    create_directory_recursive(output_dir)

    sub_directories = get_first_level_subdirectories(raw_folder_path)
    for index, item in enumerate(sub_directories):
        print(index, ":", item)
        # 生成json文件
        create_json_record(item, output_dir, include_word_list)


def after_dispose():
    record_data_list = read_json_to_dict(TEMPORARY_JSON)
    for index, item in enumerate(record_data_list):
        print("after_dispose index:", index)
        if item[KEY_ABSTRACT_TYPE] == TYPE_VOICE:
            execute_shell_command(item[KEY_FFMPEG_COMMAND])
        elif item[KEY_ABSTRACT_TYPE] == TYPE_SLIENT:
            move_file(item[KEY_VIDEO_PATH], item[KEY_NEW_FILE_PATH])
        else:
            print("after_dispose: Unknown Wrong")


def gain_command_line_argument():
    string_doc = "command-line arguments:-p Number, -h View help"
    parser = argparse.ArgumentParser(description=string_doc)
    parser.add_argument(
        "-p",
        type=str,
        help=HTLP_DOCUMENT,
        default="",
    )
    args = parser.parse_args()
    print(f"command line parameter values obtained: {args.p}")
    return args.p


def primary(output_dir, raw_folder_path, voice_include_words):
    param = gain_command_line_argument()
    if param == "0":
        print("previous process Start")
        prepare_process(raw_folder_path, output_dir, voice_include_words)
    elif param == "1":
        print("post-processing Start")
        after_dispose()
    else:
        print("Unknown command-line parameter value")
        print(HTLP_DOCUMENT)


if __name__ == "__main__":
    raw_folder_path = r"D:\999"
    output_dir = r"D:\out"

    voice_include_words = ["hello"]
    primary(output_dir, raw_folder_path, voice_include_words)
